﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccess = Microsoft.MSE.Catalog.Module.Data;
using DTO = Microsoft.MSE.Catalog.Contracts;
using System.ServiceModel;
using System.Xml;
using System.ServiceModel.Channels;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using System.Data;

namespace Microsoft.MSE.Catalog.Module
{
    internal class RepositoryChangeListenerState
    {
        public TimeSpan PollingInterval { get; set; }
        public TimeSpan MessageExpiration { get; set; }
    }
    public class RepositoryChangeManager
    {
        private static bool m_CancelTasks = false;
        

        public static string NotificationServiceAddress { get; set; }
        public static XmlElement NotificationServiceBinding { get; set; }

        public static void StartMonitor(TimeSpan pollingInterval, TimeSpan messageExpiration)
        {
            WaitCallback wc = new WaitCallback(RepositoryChangeListener);
            RepositoryChangeListenerState state = new RepositoryChangeListenerState()
            {
                PollingInterval = pollingInterval, MessageExpiration = messageExpiration
            };
            System.Threading.ThreadPool.QueueUserWorkItem(wc, state);
        }
        public static void StopMonitor()
        {
            m_CancelTasks = true;
        }

        [Obsolete("Use the RepositoryChange.RecordChange method instead.")]
        public static void RecordChange(DTO.DBEvents eventType, DTO.DBChangeType changeType, List<DTO.DBEventDataItem> eventData, SqlTransaction txn, SqlConnection conn)
        {
            DataAccess.RepositoryChange.Save(eventType, changeType, eventData, txn, conn);
        }

        #region background worker
        /// <summary>
        /// Worker loop for poling and processing messages
        /// </summary>
        /// <param name="state"></param>
        private static void RepositoryChangeListener(object state)
        {
            try
            {
                bool messagesExist = false;
                RepositoryChangeListenerState config = state as RepositoryChangeListenerState;
                if (config == null)
                {
                    //should never happen, but deffer to defaults if not state was provided
                    config = new RepositoryChangeListenerState() { PollingInterval = TimeSpan.FromSeconds(10), MessageExpiration = TimeSpan.FromMinutes(10) };
                }

                int listenerFailures = 0;
                while (!m_CancelTasks)
                {
                    //if previous iteration didn't find a message, sleep
                    if (!messagesExist)
                        SafeSleep(config.PollingInterval);

                    if (m_CancelTasks)
                        break;

                    DTO.DBEventContext message = null;
                    using (SqlConnection conn = new SqlConnection(Configurator.svcConfig.DBConnString))
                    {
                        try
                        {
                            conn.Open();
                            using (SqlTransaction tran = conn.BeginTransaction())
                            {
                                try
                                {
                                    message = DataAccess.RepositoryChange.GetRepositoryChange(config.MessageExpiration, out messagesExist, tran);
                                    if (listenerFailures != 0)
                                    {
                                        //write entry indicating connectivity restored
                                        TraceHelper.TraceInformation("Connectivity Restored.  Listening for repository changes.");
                                        listenerFailures = 0;
                                    }
                                }
                                catch (System.Exception ex)
                                {
                                    //log message every 10th failure
                                    if (listenerFailures++ % 10 == 0)
                                    {
                                        TraceHelper.TraceError("Exception occured listening for repository changes. [{0}]", CatalogSvcHelper.GetExceptionDetails(ex));
                                    }
                                }
                                try
                                {
                                    ProcessMessage(message);
                                    try { tran.Commit(); }
                                    catch { }
                                }
                                catch (CommunicationException ex)
                                {
                                    TraceHelper.TraceError("Communication Exception occured delivering change record to Notification service. \nException: {0}", CatalogSvcHelper.GetExceptionDetails(ex));
                                }
                                catch (System.Exception ex)
                                {
                                    TraceHelper.TraceError("Exception occured delivering change record to Notification service. \nException: {0}", CatalogSvcHelper.GetExceptionDetails(ex));
                                }
                            }
                        }
                        catch (System.Exception ex)
                        {
                            if (listenerFailures++ % 10 == 0)
                            {
                                TraceHelper.TraceError("Exception occured listening for repository changes.  Error connecting to repository. [{0}]", CatalogSvcHelper.GetExceptionDetails(ex));
                            }
                        }
                    }
                }
            }
            catch (System.Exception ex)
            {
                TraceHelper.TraceError("Critical exception occured listening for repository changes.  Monitoring terminated. Error: [{0}]", CatalogSvcHelper.GetExceptionDetails(ex));
            }
        }

        /// <summary>
        /// Calls Change Notification Service to send notifications to runtimes
        /// </summary>
        /// <param name="message"></param>
        private static void ProcessMessage(DTO.DBEventContext message)
        {
            if (message == null)
                return;

            //call Change Notification Service to process the message
            DTO.AcceptEventMsg aem = new DTO.AcceptEventMsg(message);
            Binding binding = BindingHelper.DeserializeBinding((XmlElement)NotificationServiceBinding);
            ChannelFactory<DTO.IChangeNotificationService> cf = new ChannelFactory<DTO.IChangeNotificationService>(binding);
            DTO.IChangeNotificationService svc = cf.CreateChannel(new EndpointAddress(NotificationServiceAddress));
            svc.AcceptEvent(aem);
        }

        /// <summary>
        /// idles the thread in 3 second intervals up to the interval specified in order to stay responsive to shutdown requests
        /// </summary>
        /// <param name="interval">total duration to sleep</param>
        private static void SafeSleep(TimeSpan interval)
        {
            TimeSpan sleepInterval = TimeSpan.FromSeconds(3);
            
            if (interval.CompareTo(sleepInterval) < 0)
                sleepInterval = interval;

            while (!m_CancelTasks && interval.TotalSeconds > 0) 
            {
                Thread.Sleep(sleepInterval);
                interval = interval.Subtract(sleepInterval);
                
                //the next sleep interval may need to be less than the default 3 seconds
                if (interval.TotalSeconds > 0 && interval.CompareTo(sleepInterval) < 0)
                    sleepInterval = interval;
            }

        }
        #endregion

    }
}
